#pragma once

#include <iostream>
#include <string>
#include <sys/select.h>
#include "Sock.hpp"
#include "Log.hpp"
#include <cstring>

const static int gport = 8888;

#define READ_EVENT (0x1 << 0) // 读事件
#define WRITE_EVENT (0x1 << 1)
#define EXCEPT_EVENT (0x1 << 2)

typedef struct FdEvent
{
    int fd;
    uint8_t event; // 只关心3位就行了
    std::string clientip;
    uint16_t clientport;
} type_t;

// typedef int type_t;
// static const int defaultfd = -1; // 暂时,Sock.hpp中有，就不定义了

static const int defaultevent = 0;

class SelectServer
{
    static const int N = (sizeof(fd_set) * 8);

public:
    SelectServer(uint16_t port = gport) : port_(port)
    {
    }
    void InitServer()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();

        for (int i = 0; i < N; i++)
        {
            fdarray_[i].fd = defaultfd;
            fdarray_[i].event = defaultevent;
            fdarray_[i].clientport = 0;
        }
    }
    void Accepter()
    {
        std::cout << "有一个新链接到来了" << std::endl;
        // 这里再进行Accept会不会被阻塞？不会，因为我们已经检测到对应的listensock_已经有就绪读事件就绪了
        std::string clientip;
        uint16_t clientport;
        int sock = listensock_.Accept(&clientip, &clientport);
        if (sock < 0)
            return;
        // 得到了对应的sock，我们可不可以进行read/recv，读取sock? 不能
        // 虽然已经得到了新的fd，但是你怎么知道sock上有数据就绪了？
        // 我把你连接建立好，但是我不给你发数据，那么此时read/recv，该套接字上的数据就一直是不就绪的，read/recv就被阻塞了，这里被阻塞，不就是整个进程被阻塞了
        // 所以我们需要将sock交给select，让select进行管理

        // 我们未来不断有listensock_在就绪，在就绪过程中，文件描述符会变多，变多之后，
        // 把对应的文件描述符交给select的本质，就是把他设置进rfds，不管怎么设置进rfds，这是一个输入输出型参数，
        // 当select返回的时候，rfds的位图大部分会被清空，
        // 你怎么保证我们对应的一个套接字在下下次再进行select的时候，依旧是让select监听呢？
        // 因为我们对一个文件描述符，有10个连接挂上了，可能当前只有一个就绪了，那么剩下9个还要select给我关心，对端在未来的某个时间点还要发数据
        // 你怎么保证你要对特定的文件描述符要有持续的监控能力呢？
        // 所以有了新功能，select服务器，使用的时候，需要程序员自己维护一个第三方数组，来进行对已经获得的sock进行管理

        logMessage(Debug, "[%s:%d],sock: %d", clientip.c_str(), clientport, sock);
        // 要让select，帮我们进行关心，只要把sock添加到fdarray_[]里面即可
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (fdarray_[pos].fd == defaultfd)
                break;
        }
        if (pos >= N)
        {
            close(sock);
            logMessage(Warning, "sockfd array[] full");
        }
        else
        {
            fdarray_[pos].fd = sock;
            // fdarray_[pos].event = (READ_EVENT| WRITE_EVENT);//同时关心读和写
            fdarray_[pos].event = READ_EVENT;
            fdarray_[pos].clientip = clientip;
            fdarray_[pos].clientport = clientport;
        }
    }

    void Recver(int index)
    {
        // 普通文件描述符就绪
        //  ServiceIO();//暂时不弄他
        int fd = fdarray_[index].fd;
        char buffer[1024];
        ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0); // 读取会被阻塞吗？
        // 不会，在这里读取的时候，读取一次，一定不会被阻塞，已经有当前事件就绪了
        if (s > 0)
        {
            buffer[s - 1] = 0;
            std::cout <<fdarray_[index].clientip << " : "<<fdarray_[index].clientport << buffer << std::endl;

            // 发送回去,也要被select管理的
            std::string echo = buffer;
            echo += " [selsect server echo] ";
            send(fd, echo.c_str(), echo.size(), 0);
        }
        else
        {
            if (s == 0)
            {
                logMessage(Info, "client quit..., fdarray[i] -> defaultfd: %d->%d", fd, defaultfd);
            }
            else

                logMessage(Warning, "recv error, client quit..., fdarray[i] -> defaultfd: %d->%d", fd, defaultfd);
            close(fdarray_[index].fd);
            fdarray_[index].fd = defaultfd;
            fdarray_[index].event = defaultevent;
            fdarray_[index].clientip.resize(0);
            fdarray_[index].clientport = 0;
        }
    }
    // echo server
    void HandlerEvent(fd_set &rfds, fd_set &wfds)
    {
        for (int i = 0; i < N; i++) // 检测是否合法
        {
            if (fdarray_[i].fd == defaultfd)
                continue;

            if ((fdarray_[i].event & READ_EVENT) && (FD_ISSET(fdarray_[i].fd, &rfds))) // 想关系读事件并且发生了
            {
                // 处理读取，1、accept，2、recv
                //  if (FD_ISSET(listensock_.Fd(), &rfds)) // 判断这个listensock_在不在文件描述符集里，也就是判断这个listensock_就绪没有
                if (fdarray_[i].fd == listensock_.Fd()) // 必须是listensock_套接字而且读事件已经就绪，才给我处理
                {
                    Accepter(); // 新连接到来的时候，就处理这个新连接，Accepter
                }
                else if (fdarray_[i].fd != listensock_.Fd()) // 到这里，说明不是listensock_套接字但是是合法的套接字才检测对应的套接字有没有就绪
                {
                    Recver(i);
                }
                else
                {}
            }
            else if((fdarray_[i].event & WRITE_EVENT)&& (FD_ISSET(fdarray_[i].fd, &wfds)))
            {

            }
            else
            {}
        }
    }

    void Start()
    {
        // demo1版
        fdarray_[0].fd = listensock_.Fd();
        fdarray_[0].event = READ_EVENT;
        while (true)
        {
            // struct timeval timeout = {2, 0};//每次对这个时间都要做重置
            // int n = select(listensock_.Fd() + 1, &rfds, nullptr, nullptr, &timeout);//对struct timeval一般设为null，方便测试，阻塞等待
            // 因为rfds是一个输入输出型参数，注定了每次都要对rfds进行重置，只要重置，我们必定要知道我们历史上都有哪些fd？所以我们把这些fd保存到了fdarray_[]的数字里
            // 因为服务器在运行中，我们对应的sockfd的值一直在动态变化，所以maxfd也一定在变化，所以写listensock_.Fd() + 1是错的
            // 所以maxfd是不是也要进行动态更新，fdarray_[]，所以有demo1版
            fd_set rfds;
            fd_set wfds;
            FD_ZERO(&rfds); // 初始化
            FD_ZERO(&wfds); // 初始化

            int maxfd = fdarray_[0].fd;
            for (int i = 0; i < N; i++)
            {
                if (fdarray_[i].fd == defaultfd) // 当前位置没有被占用
                    continue;
                // 走到这，一定是合法fd
                if (fdarray_[i].event & READ_EVENT)
                    FD_SET(fdarray_[i].fd, &rfds);
                if (fdarray_[i].event & WRITE_EVENT)
                    FD_SET(fdarray_[i].fd, &wfds);
                if (maxfd < fdarray_[i].fd)
                    maxfd = fdarray_[i].fd;
            }
            int n = select(maxfd + 1, &rfds, &wfds, nullptr, nullptr); // 这就是赵六
            switch (n)
            {
            case 0:
                logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
                break;
            case -1:
                logMessage(Warning, "%d: %s", errno, strerror(errno));
                break;
            default:
                // 成功了
                logMessage(Debug, "有一个就绪事件发生了: %d", n);
                HandlerEvent(rfds, wfds);
                DebugPrint();
                break;
            }
        }
    }
    void DebugPrint()
    {
        std::cout << "fdarray[]: ";
        for (int i = 0; i < N; i++)
        {
            if (fdarray_[i].fd == defaultfd)
                continue;
            std::cout << fdarray_[i].fd << " ";
        }
        std::cout << "\n";
    }
    ~SelectServer()
    {
        listensock_.Close();
    }

private:
    uint16_t port_;
    Sock listensock_;
    type_t fdarray_[N]; // 管理所以的fd
};